/*
 * cache.c
 *
 *  Created on: 2012-7-3
 *  Author: zyh
 */
#include "filesys/cache.h"
#include "threads/palloc.h"
#include "userprog/pagedir.h"
#include "threads/thread.h"
#include "threads/malloc.h"
#include "lib/string.h"

#define CACHE_MAXN 64


static struct list cache_list;
static struct list_elem* clock_pointer;
static void* cache_head;
static int cache_cnt;
static struct lock cache_lock;

void cache_init()
{
	list_init(&cache_list);
	lock_init(&cache_lock);
	cache_head = palloc_get_multiple(0,8);
	clock_pointer = list_begin(&cache_list);
	cache_cnt = 0;
}

/*static void print_cache()
{
	struct list_elem* e;
	struct cache* c;
	printf("*****************************\n");
	for(e = list_begin(&cache_list); e != list_end(&cache_list); e = list_next(e)){
		c = list_entry(e,struct cache,elem);
		printf("--cache,device:%s--sector:%d--buffer:%p--exist:%d\n",
				block_name(c->device),c->sector,c->buffer,c->exist);
	}
	printf("*****************************\n");
}*/

inline static bool is_full()
{
	return cache_cnt >= CACHE_MAXN;
}

static struct cache* cache_get(struct block* device,block_sector_t sector)
{
	struct list_elem* e;
	struct cache* c;
	for(e = list_begin(&cache_list); e != list_end(&cache_list); e = list_next(e)){
		c = list_entry(e,struct cache,elem);
		if(c->device == device && c->sector == sector){
			return c;
		}
	}
	return NULL;
}

static struct cache* select_cache()
{
	struct cache* c;
	while(true){
		if(clock_pointer == list_end(&cache_list)){
			clock_pointer = list_begin(&cache_list);
		}
		c = list_entry(clock_pointer,struct cache,elem);
		if(c->exist){
			if(c->accessed){
				c->accessed = false;
				clock_pointer = list_next(clock_pointer);
			}else{
				clock_pointer = list_next(clock_pointer);
				return c;
			}
		}else{
			clock_pointer = list_next(clock_pointer);
		}
	}
}

static void cache_evict(struct cache* wanter)
{
	struct cache* evictee = select_cache();
	if(evictee->dirty){
		block_write(evictee->device,evictee->sector,evictee->buffer);
	}
	evictee->exist = false;
	wanter->exist = true;
	wanter->buffer = evictee->buffer;
}

static void cache_add(struct block* device,block_sector_t sector)
{
	struct cache* new_one = malloc(sizeof(struct cache));
	cache_cnt++;
	new_one->device = device;
	new_one->sector = sector;
	new_one->accessed = true;
	new_one->dirty = true;
	new_one->exist = true;
	if(!is_full()){
		new_one->buffer = cache_head + BLOCK_SECTOR_SIZE * (cache_cnt - 1);
	}else{
		cache_evict(new_one);
	}
	list_push_back(&cache_list,&new_one->elem);
}


static void read_from_block(struct cache* c)
{
	block_read(c->device,c->sector,c->buffer);
	/*struct cache* next = list_entry(list_next(c->elem),struct cache,elem);
	if(next != NULL){
		if(!next->exist){
			printf("-------next sector:%d\n",next->sector);
			next->locked = true;
			cache_evict(next);
			next->locked = false;
			block_read(next->device,next->sector,next->buffer);
		}
	}*/
}

void write_to_cache(struct block* device,block_sector_t sector,int offset,int size,void* buffer)
{
	  struct cache* ctmp = cache_get(device,sector);
	  if(ctmp == NULL){
		  cache_add(device,sector);
		  ctmp = cache_get(device,sector);
		  memcpy(ctmp->buffer + offset,buffer,size);
	  }else{
		  if(!ctmp->exist){
			  cache_evict(ctmp);
		  }
		  memcpy(ctmp->buffer + offset,buffer,size);
		  ctmp->accessed = true;
		  ctmp->dirty = true;
	  }
}

void read_from_cache(struct block* device,block_sector_t sector,int offset,int size,void* buffer)
{
	  struct cache* ctmp = cache_get(device,sector);
	  if(ctmp == NULL){
		  cache_add(device,sector);
		  ctmp = cache_get(device,sector);
		  read_from_block(ctmp);
	  }else{
		  if(!ctmp->exist){
			  cache_evict(ctmp);
			  read_from_block(ctmp);
		  }
	  }
	  memcpy(buffer,ctmp->buffer + offset,size);
	  ctmp->accessed = true;
}

void write_all_to_block()
{
	struct list_elem* e;
	struct cache* c;
	for(e = list_begin(&cache_list); e != list_end(&cache_list); e = list_next(e)){
		c = list_entry(e,struct cache,elem);
		if(c->exist && c->dirty){
			block_write(c->device,c->sector,c->buffer);
		}
	}
}

